package com.gateway.server.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gateway.common.enums.ConfigGroupEnum;
import com.gateway.common.enums.DataEventTypeEnum;
import com.gateway.common.enums.DeletedEnum;
import com.gateway.common.enums.RpcTypeEnum;
import com.gateway.register.common.dto.MetaDataRegisterDTO;
import com.gateway.register.common.dto.RouteRegisterDTO;
import com.gateway.repository.bean.GatewayApiMetaData;
import com.gateway.repository.bean.GatewayFilterDefinition;
import com.gateway.repository.bean.GatewayPredicateDefinition;
import com.gateway.repository.bean.GatewayRouteDefinition;
import com.gateway.repository.service.GatewayApiMetaDataService;
import com.gateway.repository.service.GatewayFilterDefinitionService;
import com.gateway.repository.service.GatewayPredicateDefinitionService;
import com.gateway.repository.service.GatewayRouteDefinitionService;
import com.gateway.server.helper.GatewayApiMetadataHelper;
import com.gateway.server.helper.GatewayRouteHelper;
import com.gateway.server.listener.DataChangedEvent;
import com.gateway.common.dto.route.FilterDefinitionDTO;
import com.gateway.common.dto.route.PredicateDefinitionDTO;
import com.gateway.common.dto.route.RouteDefinitionDTO;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @program: api-gateway
 * @description:
 * @author: YuKai Fan
 * @create: 2025/2/4 21:55
 **/
@Service("springCloud")
public class GatewayClientRegisterSpringCloudServiceImpl implements GatewayClientRegisterService {

    private static final Logger log = LoggerFactory.getLogger(GatewayClientRegisterSpringCloudServiceImpl.class);
    @Resource
    private ApplicationEventPublisher publisher;
    @Resource
    private GatewayApiMetaDataService gatewayApiMetaDataService;
    @Resource
    private GatewayRouteDefinitionService gatewayRouteDefinitionService;
    @Resource
    private GatewayFilterDefinitionService gatewayFilterDefinitionService;
    @Resource
    private GatewayPredicateDefinitionService gatewayPredicateDefinitionService;
    @Override
    public String rpcType() {
        return RpcTypeEnum.SPRING_CLOUD.getName();
    }

    @Override
    public String register(MetaDataRegisterDTO metaDataRegisterDTO) {
        GatewayApiMetaData existedGatewayApiMetaData = gatewayApiMetaDataService.getOne(
                new QueryWrapper<GatewayApiMetaData>().lambda()
                        .eq(GatewayApiMetaData::getEnv, metaDataRegisterDTO.getEnv())
                        .eq(GatewayApiMetaData::getAppId, metaDataRegisterDTO.getAppId())
                        .eq(GatewayApiMetaData::getServiceId, metaDataRegisterDTO.getServiceId())
                        .eq(GatewayApiMetaData::getApiPath, metaDataRegisterDTO.getApiPath())
                        .eq(GatewayApiMetaData::getDeleted, DeletedEnum.NO.value)
        );
        GatewayApiMetaData gatewayApiMetaData = GatewayApiMetadataHelper.convert(metaDataRegisterDTO);
        if (Objects.isNull(existedGatewayApiMetaData)) {
            try {
                gatewayApiMetaDataService.save(gatewayApiMetaData);
            } catch (DuplicateKeyException e) {
                // 可能存在并发问题，目前可以忽略不处理
                log.error("接口元数据异常, 原因为: [{}]重复新增", metaDataRegisterDTO.getApiPath());
            }
        } else {
            gatewayApiMetaData.setId(existedGatewayApiMetaData.getId());
            gatewayApiMetaDataService.updateById(gatewayApiMetaData);
        }
        return "success";
    }

    @Override
    public String registerRoute(String name, RouteRegisterDTO routeRegisterDTO) {
        RouteDefinitionDTO routeDefinitionDTO = buildRoute(routeRegisterDTO);
        String appId = routeDefinitionDTO.getAppId();
        if (Objects.isNull(appId)) {
            log.warn("appId为空的路由不处理");
            return "failed";
        }
        GatewayRouteDefinition existedGateRouteDefinition = gatewayRouteDefinitionService.getOne(
                new QueryWrapper<GatewayRouteDefinition>().lambda()
                        .eq(GatewayRouteDefinition::getAppId, routeDefinitionDTO.getAppId())
                        .eq(GatewayRouteDefinition::getRouteId, routeDefinitionDTO.getId())
                        .eq(GatewayRouteDefinition::getDeleted, DeletedEnum.NO.value)
        );
        GatewayRouteDefinition routeDefinition = GatewayRouteHelper.convert(routeDefinitionDTO);
        DataEventTypeEnum eventType;
        if (Objects.isNull(existedGateRouteDefinition)) {
            try {
                List<GatewayFilterDefinition> gatewayFilterDefinitions = GatewayRouteHelper.convertToFilter(routeDefinitionDTO);
                List<GatewayPredicateDefinition> gatewayPredicateDefinitions = GatewayRouteHelper.convertToPredicate(routeDefinitionDTO);
                gatewayRouteDefinitionService.save(routeDefinition);
                gatewayFilterDefinitionService.saveBatch(gatewayFilterDefinitions);
                gatewayPredicateDefinitionService.saveBatch(gatewayPredicateDefinitions);
                // 初始化过滤器和断言器
                eventType = DataEventTypeEnum.CREATE;
            } catch (DuplicateKeyException e) {
                // 可能存在并发问题，目前可以忽略不处理
                return "ignore";
            }
        } else {
            routeDefinition.setId(existedGateRouteDefinition.getId());
            gatewayRouteDefinitionService.updateById(routeDefinition);
            eventType = DataEventTypeEnum.UPDATE;
        }
        // 发布路由
        publisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.ROUTE, eventType, routeDefinition));
        return "success";
    }

    protected RouteDefinitionDTO buildRoute(RouteRegisterDTO registerDTO) {
        // 设置predicates
        List<PredicateDefinitionDTO> predicates = Lists.newArrayList();
        //源代码中就是这么定义的在PredicateDefinition类中的低35行代码
        String generateName = GatewayRouteHelper.generateName(0);
        Map<String, String> args = Maps.newHashMap();
        args.put(generateName, GatewayRouteHelper.generatePath(registerDTO.getServiceId()));
        PredicateDefinitionDTO predicateDefinition = PredicateDefinitionDTO.Builder.builder()
                .name("Path")
                .args(args)
                .actionable(false)
                .build();
        //Path 后的内容可以是多个路径，多个路径用逗号隔开
        predicates.add(predicateDefinition);

        // 设置filters
        List<FilterDefinitionDTO> filters = Lists.newArrayList();
        Map<String, String> argsFilter = Maps.newHashMapWithExpectedSize(20);
        String generateFilterKey = GatewayRouteHelper.generateName(0);
        argsFilter.put(generateFilterKey, "1");
        FilterDefinitionDTO filterDefinition = FilterDefinitionDTO.Builder.builder()
                .name("StripPrefix")
                .args(argsFilter)
                .actionable(false)
                .build();
        filters.add(filterDefinition);

        URI uri;
        try {
            uri = new URI("lb://" + registerDTO.getServiceId());
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        return RouteDefinitionDTO.Builder.builder()
                .appId(registerDTO.getAppId())
                .id(registerDTO.getServiceId())
                .rpcType(registerDTO.getRpcType())
                .env(registerDTO.getEnv())
                .appName(registerDTO.getAppName())
                .serviceId(StringUtils.defaultIfBlank(registerDTO.getServiceId(), registerDTO.getAppName()))
                .contextPath(registerDTO.getContextPath())
                .uri(uri)
                .predicates(predicates)
                .filters(filters)
                .order(0)
                .metadata(new HashMap<>(2))
                .build();
    }
}
